[Python]boto3からGuardDutyへのアクセスをmotoでモックしてみた
はじめに
最近PythonでAWSサービス関連操作をモックしてくれる「moto」というライブラリを使って開発をしているのですが、GuardDuty周りも一部対応しているようなので試してみました。
boto3を使っている方はテストを書く時とっても便利なので、以下の記事も合わせてご覧ください。
環境
以下の環境で実施してます。
環境 | バージョン |
---|---|
Python | 3.8.13 |
Poetry | 1.2.2 |
moto | 4.1.9 |
boto3 | 1.26.135 |
pytest | 7.3.1 |
やってみる
motoのドキュメントを確認したところ、create_detector
とget_detector
は対応しているようだったので、これらを使ってテストを書いてみます。(その他にいくつか対応しているものはあったので、こちらから確認してみてください。)
テスト対象コード
まずはGuardDutyを有効化する関数をテストしてみます。
以下のコードではDataSourcesとしてS3Logsを有効にしてGuardDutyを有効化しています。
import boto3 def create_guardduty_detector(client): response = client.create_detector( Enable=True, DataSources={ 'S3Logs': { 'Enable': True } } ) detector_id = response['DetectorId'] return detector_id
テストコード
テストでmotoを利用するには、各AWSサービスに応じたモックを用意します。
motoからGuardDutyに対応するmock_guardduty
をインポートして、テスト対象の関数にデコレーター(@mock_guardduty
)として記述してください。
このテストコードでは、clientを取得し、テスト対象コード内のcreate_guardduty_detector
をモック内で実行します。この時、guarddutyのcreate_detector
が実行されますが、テストコード側で@mock_guardduty
を記述しているためAWS環境でGuardDutyが有効化されることはありません。
get_detector
を実行することで、モック内で有効化したGuardDutyのdetectorから情報を取得して出力してみます。
from moto import mock_guardduty import main import boto3 @mock_guardduty def test_create_guardduty_detector_success(): client = boto3.client('guardduty', region_name='ap-northeast-1') detector_id = main.create_guardduty_detector(client) res = client.get_detector(DetectorId=detector_id) print(res) assert res["Status"] == "ENABLED" # S3Logsが有効化されていることを確認 assert res["DataSources"]["S3Logs"]["Status"] == "ENABLED"
最後のassertでは、取得したdetectorの情報からDataSourcesのS3Logsが有効化されているか?をテストしています。
実行結果
テストを実行してみると、問題なくパスしました。 有効化したGuardDutyは、DataSourcesとしてS3Logsを有効されていることが確認できました。
.venv ❯ poetry run pytest -s ===================================================================== test session starts ====================================================================== platform darwin -- Python 3.8.13, pytest-7.3.1, pluggy-1.0.0 rootdir: /Users/suzuki.jun/Documents/moto-guardduty collected 1 item test_main.py {'ResponseMetadata': {'HTTPStatusCode': 200, 'HTTPHeaders': {}, 'RetryAttempts': 0}, 'CreatedAt': '2023-05-18T15:47:46.755182Z', 'FindingPublishingFrequency': 'SIX_HOURS', 'ServiceRole': 'arn:aws:iam::123456789012:role/aws-service-role/guardduty.amazonaws.com/AWSServiceRoleForAmazonGuardDuty', 'Status': 'ENABLED', 'UpdatedAt': '2023-05-18T15:47:46.755182Z', 'DataSources': {'CloudTrail': {'Status': 'DISABLED'}, 'DNSLogs': {'Status': 'DISABLED'}, 'FlowLogs': {'Status': 'DISABLED'}, 'S3Logs': {'Status': 'ENABLED'}, 'Kubernetes': {'AuditLogs': {'Status': 'DISABLED'}}}, 'Tags': {}} . ====================================================================== 1 passed in 0.37s =======================================================================
print(res)
で出力しているdetectorの情報が見づらいので整形してみます。
有効化した際に指定したS3LogsがENABLEDになっていて、ServiceRoleなどのARNはサンプルのアカウントIDに置き換えられていますね。
{ "ResponseMetadata": { "HTTPStatusCode": 200, "HTTPHeaders": {}, "RetryAttempts": 0 }, "CreatedAt": "2023-05-18T15:45:31.213323Z", "FindingPublishingFrequency": "SIX_HOURS", "ServiceRole": "arn:aws:iam::123456789012:role/aws-service-role/guardduty.amazonaws.com/AWSServiceRoleForAmazonGuardDuty", "Status": "ENABLED", "UpdatedAt": "2023-05-18T15:45:31.213323Z", "DataSources": { "CloudTrail": { "Status": "DISABLED" }, "DNSLogs": { "Status": "DISABLED" }, "FlowLogs": { "Status": "DISABLED" }, "S3Logs": { "Status": "ENABLED" }, "Kubernetes": { "AuditLogs": { "Status": "DISABLED" } } }, "Tags": {} }
わざと失敗させてみる
テスト対象コードを以下のように、S3LogsをFalseに変更してみます。
import boto3 def create_guardduty_detector(client): response = client.create_detector( Enable=True, DataSources={ 'S3Logs': { 'Enable': False } } ) detector_id = response['DetectorId'] return detector_id
この状態でテストを実行すると、想定通り失敗しました。
テストコード上のassertで、res["DataSources"]["S3Logs"]["Status"]
の値で期待していたのはENABLED
のはずが、実際は有効化されていないためDISABLED
になっていると怒られます。分かりやすいですね。
=========================================================================== FAILURES =========================================================================== ____________________________________________________________ test_create_guardduty_detector_success ____________________________________________________________ @mock_guardduty def test_create_guardduty_detector_success(): client = boto3.client('guardduty', region_name='ap-northeast-1') detector_id = main.create_guardduty_detector(client) res = client.get_detector(DetectorId=detector_id) print(res) assert res["Status"] == "ENABLED" > assert res["DataSources"]["S3Logs"]["Status"] == "ENABLED" E AssertionError: assert 'DISABLED' == 'ENABLED' E - ENABLED E + DISABLED test_main.py:14: AssertionError =================================================================== short test summary info ==================================================================== FAILED test_main.py::test_create_guardduty_detector_success - AssertionError: assert 'DISABLED' == 'ENABLED' ====================================================================== 1 failed in 0.37s =======================================================================
まとめ
motoを使ったGuardDutyのテストを試してみました。テストコード側にインポートしたmotoのライブラリをデコレーターとして記述するだけなのでとても便利です。 AWS側のアップデートに追いついていない部分もあったりしますが、テストコードをサクッと書けるので是非利用してみてください。